/*-
 * Copyright (c) 2009-2010 David Forsythe.
 * See LICENSE file for license details.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fts.h>
#include <limits.h>

/* Cool kids club. */
#include "internal.h"
#include "freebsd_database_directorydb.h"

struct _directorydb {
        char *location[2];
        FTS *ftsp;
};

static int _fts_entry_compare(const FTSENT * const *, const FTSENT * const *);
static int _read_pkg(struct pkg_database *,  FTSENT *, struct pkg *, u_int);
static int _write_pkg(struct pkg_database *, struct pkg *, u_int);
static int _pkg_database_directorydb_open(struct pkg_database *, const char *, 
        u_int);
static int _pkg_database_directorydb_rewind(struct pkg_database *);
static int _pkg_database_directorydb_close(struct pkg_database *);
static int _pkg_database_directorydb_add_pkg(struct pkg_database *, 
        struct pkg *, u_int);
static int _pkg_database_directorydb_remove_pkg(struct pkg_database *, 
        const char *);
static int _pkg_database_directorydb_get_next_pkg(struct pkg_database *, 
        struct pkg *, u_int);

static struct pkg_database_vtable _vt = {
        .open = _pkg_database_directorydb_open,
        .close = _pkg_database_directorydb_close,
        .add_pkg = _pkg_database_directorydb_add_pkg,
        .remove_pkg = _pkg_database_directorydb_remove_pkg,
        .rewind = _pkg_database_directorydb_rewind,
        .get_next_pkg = _pkg_database_directorydb_get_next_pkg
};


/* Initialize and return a static vtable for directorydb ops. */
struct pkg_database_vtable *
pkg_database_directorydb_vtable(void)
{
#if 0
	static struct pkg_database_vtable vt;
        
        vt.open = _pkg_database_directorydb_open;
        vt.close = _pkg_database_directorydb_close;
        vt.add_pkg = _pkg_database_directorydb_add_pkg;
        vt.remove_pkg = _pkg_database_directorydb_remove_pkg;
        vt.rewind = _pkg_database_directorydb_rewind;
        vt.get_next_pkg = _pkg_database_directorydb_get_next_pkg;
#endif
	return (&_vt);
}

void
pkg_database_directorydb_init(struct pkg_database *db)
{
        _pkg_database_set_vtable(db, &_vt);
}

const char *
pkg_database_directorydb_type_name(void)
{
	return ("directorydb");
}

static int
_pkg_database_directorydb_open(struct pkg_database *db, const char *path, 
        u_int flags)
{
        struct _directorydb *d;

        (void)flags;

        d = calloc(1, sizeof(*d));
        if (d == NULL) {
                return (PKG_NOT_OK);
        }
        
        /* Initialize everything. */
        d->location[0] = strdup(path);
        d->location[1] = NULL;

        /* Use fts here so we can get ordering and a quick refresh. */
        d->ftsp = fts_open(d->location, FTS_LOGICAL | FTS_NOCHDIR | FTS_NOSTAT, 
                _fts_entry_compare);

        if (d->ftsp == NULL) {
                PKG_CLIENT_CRASH(__func__, "Failed to open database.");
        }

        db->internal = d;

        return (PKG_OK);
}

static int
_pkg_database_directorydb_rewind(struct pkg_database *db)
{
        struct _directorydb *d;

        d = db->internal;

        fts_close(d->ftsp);
        d->ftsp = fts_open(d->location, FTS_LOGICAL | FTS_NOCHDIR | FTS_NOSTAT,
                _fts_entry_compare);
        if (d->ftsp == NULL) {
                PKG_CLIENT_CRASH(__func__, "Failed ot open database");
        }

        return (PKG_OK);
}

static int
_pkg_database_directorydb_close(struct pkg_database *db)
{
        struct _directorydb *d;

        d = db->internal;
        fts_close(d->ftsp);
        free(d->location[0]);
        free(d);
        return (PKG_OK);
}

static int
_pkg_database_directorydb_get_next_pkg(struct pkg_database *db, struct pkg *p, 
        u_int flags) 
{
        struct _directorydb *d;
        FTSENT *f;

        d = db->internal;

        while ((f = fts_read(d->ftsp)) != NULL) {
                /* make sure this is a directory.  And that we're at the right
                 * level.  This is basically just skipping the database basedir. 
                 */
                if (f->fts_info != FTS_D || f->fts_level != 1) {
                        continue;
                }

                fts_set(d->ftsp, f, FTS_SKIP); /* We don't want the children. */
                /* Jump into _read_pkg and hope for the best. */
                return (_read_pkg(db, f, p, flags));
        }
        
        /* 'Cursor' is at the end. */
        return (PKG_NOT_OK);
}

static int
_pkg_database_directorydb_add_pkg(struct pkg_database *db, struct pkg *p, 
        u_int flags)
{
        _write_pkg(db, p, flags);
        _pkg_database_directorydb_rewind(db); 

        return (PKG_NOT_OK);
}

static int
_pkg_database_directorydb_remove_pkg(struct pkg_database *db, const char *name)
{
        /*
         * Writing this made me realize I need to take the database into memory
         * (just as a linked list or something.  We shouldn't force a rewind on
         * remove, but the cursor should break if we remove the current
         * "package".
         */
        
        struct _directorydb *d;
        FTSENT *f;

        d = db->internal;

        fts_close(d->ftsp);
        d->ftsp = fts_open(d->location, FTS_LOGICAL | FTS_NOCHDIR | FTS_NOSTAT,
                _fts_entry_compare);
        if (d->ftsp == NULL) {
                PKG_CLIENT_CRASH(__func__, "Failed to open database");
        }

        while ((f = fts_read(d->ftsp)) != NULL) {
                if (f->fts_info != FTS_D || f->fts_level != 1) {
                        continue;
                }
                fts_set(d->ftsp, f, FTS_SKIP);
                /* Confirm its in here. */
                if (STRMATCH(f->fts_name, name)) {
                }
        }
        
        /* 
         * And we're forced to rewind here because we don't know what's going on
         * with the stream.
         */
        pkg_database_rewind(db);


        return (PKG_NOT_OK);
}

static int
_fts_entry_compare(const FTSENT *const *ent1, const FTSENT *const *ent2)
{
        const FTSENT *e1;
        const FTSENT *e2;

        e1 = *ent1;
        e2 = *ent2;
        return (strcmp(e1->fts_name, e2->fts_name));
}

static int
_read_pkg(struct pkg_database *db, FTSENT *f, struct pkg *p, u_int flags)
{
        struct _directorydb *d;
        FILE *fp;
        struct meta_entry *me;
        char meta_path[PATH_MAX];
        char meta_buff[256]; /* If it gets larger than this, we can bump it. */
        int r;

        d = db->internal;

        pkg_set_key(p, f->fts_name);
        for (me = pkg_get_meta_entry_table(p); me->name != NULL; me++) {
                if (!FLAG_CHECK(flags, me->code)) {
                        strcpy(meta_buff, "+");
                        strcat(meta_buff, me->name);
                        _pkg_util_path_join(meta_path, f->fts_path, meta_buff);
                        
                        fp = fopen(meta_path, "r");
                        if (fp == NULL) {
                                continue;
                        }
                        if (me->type == PARSEABLE) {
                                r = pkg_parse_FILE(p, fp, me->name);
                                if (r != PKG_OK) {
                                        /* Print nothing, we don't know what the
                                         * error is here, and parse_FILE should 
                                         * have printed its own message. */
                                        fclose(fp);
                                        return (r);
                                }
                        }
                        fclose(fp);
                        if (me->type == SCRIPT) {
                                /* Add this script to the script list in the
                                 * pkg. */
                                pkg_register_script(p, me->name, meta_path);
                        }
                }
        }
        
        return (PKG_OK);
}

/* Write parts of package metadata to the database. */
static int
_write_pkg(struct pkg_database *db, struct pkg *p, u_int flags)
{
        struct _directorydb *d;
        const char *key;
        struct meta_entry *me;
        char entry_path[PATH_MAX];
        char meta_path[PATH_MAX];
        char meta_buff[256];
        const char *script;
        FILE *fp;
        int r;

        d = db->internal;

        key = pkg_get_key(p);
        
        _pkg_util_path_join(entry_path, d->location[0], key);
        mkdir(entry_path, 0644); // XXX: perms
        for (me = pkg_get_meta_entry_table(p); me->name != NULL; me++) {
                if (!FLAG_CHECK(flags, me->code)) {
                        strcpy(meta_buff, "+");
                        strcat(meta_buff, me->name);
                        _pkg_util_path_join(meta_path, entry_path, meta_buff);
                        
                        if (me->type == PARSEABLE) {
                                /* Overwrite.  Don't open outside of this block
                                 * because we don't want to overwrite scripts
                                 * that are already in the database. */
                                fp = fopen(meta_path, "w");
                                r = pkg_write_FILE(p, fp, me->name);
                                if (r != PKG_OK) {
                                        fclose(fp);
                                }
                                fclose(fp);
                        } else if (me->type == SCRIPT) {
                                script = pkg_get_script_pathname(p, me->name);
                                /* Check if the script is already in the db. */
                                if (!STRMATCH(script, meta_path)) {
                                         r = rename(script, meta_path);
                                         if (r != 0) {
                                                 /* rename failed. */
                                                 return (PKG_NOT_OK);
                                         }
                                }
                                pkg_register_script(p, me->name, meta_path);
                        }
                }
        }

        return (PKG_OK);
}
